Tags

Note: This functionality is not yet implemented. This document describes how it's planned.

Tags are defined as subclasses of xjavadoc.DefaultXTag. If you want to define a class-level tag that can be used like this...

/**
 * @sql.table name="Customers"
 */

...it should be done as folows:

/**
 * @xdoclet.tag
 *   name="sql.table"
 *   class="true"
 *   inherit="false"
 */
public abstract class SqlTableTag extends xjavadoc.DefaultXTag {
   /**
    * @xdoclet.tag-attribute name="name"
    */
   public abstract String getName();
}

I'll explain further down why the class should be abstract. Defining a tag with a standard java class this way will allow us to do three important things:

  • Generate tag documentation
  • Tag validation
  • IDE code completion

Generate tag documentation

This will be done by the xtags plugin. It will generate a simple object model of the tags and use Velocity to render a document. The plugin will have a built-in Velocity template that can generate xdoc documents for further processing with Maven but it's possible to supply a custom template in order to generate tag documents in other formats.

Tag validation

There are several ways to validate tags:

  • Is the tag valid in its particular location?
  • Does the tag specify all the required parameters?
  • Is the tag completely missing?

    The first two validations can be done by the tag class itself by overriding the validate() method. Example:

       public void validate() throws xjavadoc.ValidationException {
          if( getName() == null ) {
             throw new ValidationException("The 'name' attribute must be specified.");
          }
       }

    The most simple validation rules should be able to express as tags on the tag class. If more sophisticated validation is needed, just code it in the validate() method.

    With some minor xjavadoc improvements (an XTag needs to know its position in the source), the error message could even contain the file name and the line number. -Or at least the method name if it is a method tag.

    Now I'll describe why the class should be abstract. The main reason is to keep it easier for the programmer. In fact, when a plugin is packaged, it will generate a concrete subclass of all the tag classes and implement all the abstract methods. Further, it will also implement the validate() method, so the programmer won't have to think about implementing that method. -Unless some really fancy validation is required.

    And what about deprecated tags? Easy! Let's look at the SqlTableTag class again, now with some extra tags in it:

    /**
     * @xdoclet.tag
     *   name="sql.table"
     *   class="true"
     *   inherit="false"
     */
    public abstract class SqlTableTag extends xjavadoc.DefaultXTag {
       /**
        * @xdoclet.tag-attribute
        *   name="name"
        *   required="true"                                       <--- This is new
        *   regexp="/^[a-zA-Z0-9]+$/"                             <--- This is new
        *
        * @xdoclet.tag-attribute                                  <--- This is new
        *   name="value"                                          <---
        *   class="true"                                          <---
        *   deprecated="Please use the name tag-attribute"        <---
        */
       public abstract String getName();
    }

    The presence of required="true" will be used in the generated validate() method in the generated subclass. The regexp and deprecation warning too:

    public class SqlTableTagImpl extends SqlTableTag {
       // JDK 1.4 Regular Expressions
       private static final Pattern nameRegexp = Pattern.compile("/^[a-zA-Z0-9]+$/");
    
       public String getName() {
          String result;
          // first get the official value:
          result = getAttributeValue("name");
          if( result == null ) {
             result = getAttributeValue("name");
          }
          return result;
       }
    
       public void validate() throws xjavadoc.ValidationException {
          super.validate();
          if( getName() == null ) {
             throw new ValidationException("The 'name' attribute must be specified.");
          }
          if( !nameRegexp.matcher(getName()).matches() ) {
             throw new ValidationException("The specified 'name' attribute is not valid.");
          }
       }
    }

    The default regular expression used for all tag attributes is a-zA-Z0-9.

    Warnings about missing tags can't be done by the XTag classes themselves, because if a tag is missing, there won't be any tag object. This should be done either by the Generator class or from the Velocity template, by calling a special method on a special context object:

    #set( $sqlTableTag = $class.doc.getTag( "sql" ) )
    #if( $

TagMetaData API

XJavadoc will be augmented with an API to access meta-data about various @tags. This API will provide information about where a tag is appliccable, valid tag attributes, whether they are mandatory, description and so on.

Each @tag will have a separate XML descriptor similar to the current xtags.xml. For a tag named @foo.bar, there will be a foo.bar.tag.xml descriptor.

The TagMetaData API implementation will parse these descriptors and make the information available via first-class java objects. This is very useful for IDE plugins that want to implement code-completion for @tags.

The XDoclet plugin developer can choose whether she wants to write the *.tag.xml descriptor by hand or have it generated from a tagged DefaultXTag subclass. The XDoclet SDK will provide a special XDoclet plugin generate these files.

How are the tags instantiated?

It's possible to preconfigure xjavadoc to instantiate a particular XTag class when it encounters a particular tag name. In order to be able to do that, the tag name must be mapped to the class of the tag object it is supposed to instantiate.

This is done by calling the xjavadoc.XTagFactory.registerTagClass(java.lang.String, java.lang.Class) method.

This should be done before parsing of the sources. -And what's a more natural place to define this mapping than in xdoclet-plugin.xml!

This means that when XDoclet starts up, xdoclet.PluginFactory will do this registration. The xdoclet-plugin.xml is generated from Plugin classes with @xdoclet.plugin tags. We'll just improve the plugin plugin a bit so that it can take into account the XTag classes with @xdoclet.tag tags too.

IDE code completion

In order to support code completion in IDE plugins for XDoclet, we need to provide some metadata about the tags and where they fit in. Provided that the IDE plugin actually knows enough about the code it's editing (let's hope they do!), they could benefit from a little API extension in xjavadoc. (This might actually be useful for xjavadoc's validation too!).

public interface XProgramElement {
   public Collection getLegalTags();
}

public interface XTag {
   public Collection getLegalAttributes();
}

These interfaces are defined by XJavaDoc, and have to be implemented by each IDE plugin provider. XClass defines methods that provide metadata about the class being edited. How it's implemented doesn't matter, but it has to be done by the IDE plugin provider, since the edited classes aren't compiled, and it's only the IDE that can possibly provide the required metadata. xjavadoc.SourceClass can't be used, since it requires the class to be perfect, which is probably not the case in an IDE, especially if there are a few syntax errors. But modern IDEs are smart, and they'll be able to provide a decent implementation, probably by wrapping some of the classes in the IDE core.

Then, when the user moves around in the source and types a @ the IDE will pop up a list of legal tags. When the tag is typed and space is hit, the IDE can pop up a list of legal attributes for that tag.

What's the best way to keep this info? The XTag implementation classes that are partly hand coded, partly generated is perfect for the tag attributes. But how about the XClass?

It could be done using the XTag's built-in validation logic in a slightly different manner. Just try to set each registered tag programmatically. If a ValidationException is thrown, the tag is not legal.

Tag dependencies

Some tags don't make sense unless an other tag is present. For example, @ejb.ejb-ref makes no sense unless there is a @ejb.bean tag somewhere. We need to define tag dependencies somehow. Then part of the validation logic should check that all defined dependencies are satisfied in every source file. This validation should be done entirely outside xdoclet, and only occur in the xjavadoc framework.

How to define these dependencies is still not decided, but we might do it with tags too: +------------------------------------------------------------------- /** * @xdoclet.tag * name="ejb.ejb-ref" * depends="ejb.bean" * +-------------------------------------------------------------------

Conclusion

Defining tags as classes will keep docs uptodate with the code and it provides a flexible validation mechanism and opens for code completion in IDEs